home
diamond Go Premium
Data Engineering Path  ·  Airflow
Apache Airflow Logo

Production Best Practices

🏭 Running Airflow at Scale in Production

Moving from development to production requires careful attention to reliability, monitoring, and operational best practices. This guide covers the patterns that distinguish hobby projects from enterprise-grade data platforms.


The 10 Commandments of Production Airflow

# Rule Why It Matters
1 Idempotency Every task should produce the same output when run multiple times for the same logical date
2 Atomicity Each task should do one thing and do it completely — no partial states
3 No side effects at parse time Don't make API calls or DB queries when the DAG file is imported
4 Use Connections, not hardcoded creds Security, reusability, and environment separation
5 Set retries and timeouts Self-healing pipelines that don't run forever
6 Keep DAGs simple Fewer tasks per DAG, more DAGs if needed. Max ~30 tasks per DAG
7 Use tags and descriptions Discoverability in the UI. Your future self will thank you
8 Test before deploying DagBag validation + unit tests in CI/CD
9 Monitor with SLAs and alerts Proactive detection of pipeline delays
10 Clean up old data Regular cleanup of metadata DB, logs, and XCom entries

Idempotency Pattern

# ❌ NOT idempotent — appends every time
@task()
def bad_load(data):
    hook = PostgresHook("warehouse")
    hook.insert_rows("sales", data)  # Duplicates on re-run!

# ✅ Idempotent — delete-then-insert
@task()
def good_load(data, **kwargs):
    ds = kwargs['ds']
    hook = PostgresHook("warehouse")

    # Delete existing data for this date first
    hook.run(f"DELETE FROM sales WHERE sale_date = '{ds}'")

    # Then insert fresh data
    hook.insert_rows("sales", data)

Monitoring & Alerting

from airflow.sdk import DAG
from datetime import datetime, timedelta

with DAG(
    "monitored_pipeline",
    schedule="0 6 * * *",
    start_date=datetime(2024, 1, 1),
    # SLA: entire DAG should complete within 2 hours of scheduled time
    dagrun_timeout=timedelta(hours=2),
    default_args={
        "retries": 3,
        "retry_delay": timedelta(minutes=5),
        "email_on_failure": True,
        "email_on_retry": True,
        "email": ["oncall@company.com"],
        "execution_timeout": timedelta(hours=1),  # Per-task timeout
        "sla": timedelta(hours=1),  # SLA per task
    },
    # Callback functions for DAG-level events
    on_failure_callback=send_slack_alert,
    on_success_callback=log_success_metric,
) as dag:
    ...
💡 Tip
Set up three layers of alerting: (1) Per-task email on failure, (2) DAG-level Slack notification via callback, (3) SLA miss alerts for business-critical pipelines. This ensures nothing falls through the cracks.

DAG Organization at Scale

dags/
├── __init__.py
├── common/
│   ├── __init__.py
│   ├── callbacks.py          # Shared alert functions
│   ├── quality_checks.py     # Reusable data quality tasks
│   └── constants.py          # Shared configuration
├── sales/
│   ├── __init__.py
│   ├── daily_sales_etl.py
│   └── sales_quality.py
├── marketing/
│   ├── __init__.py
│   ├── campaign_pipeline.py
│   └── attribution_model.py
└── infrastructure/
    ├── __init__.py
    ├── cleanup_metadata.py    # DB maintenance
    └── dag_health_monitor.py  # Meta-DAG monitoring
📘 Note
Organize DAGs by domain (sales, marketing, finance), not by technical layer (extract, transform, load). Each team owns their domain's DAGs, which aligns with organizational structure and code ownership.

Secure Connection Management in the Web UI

One of the most important production best practices is the separation of credential configurations from Python code (Commandment #4).

Instead of hardcoding database passwords, OAuth tokens, or AWS credentials inside your DAG scripts, you configure them securely inside the Airflow Web UI's Admin -> Connections panel:

Airflow Web UI — Connection Credentials Manager

These connections are encrypted at rest in the metadata database and are retrieved dynamically at runtime by hooks using their unique connection ID (conn_id).

lock

This content is reserved for Premium Members.

Upgrade to Premium

Entity Details

Create New Item

help

Submit Technical Query

Have a question or run into an issue? Describe it below, upload an optional screenshot, and our engineering team will answer it!

image Attach image (optional)

Submit Feedback

build Free Developer Utility Free Tool
gavel

Privacy & Legal Disclaimer

1. Client-Side Browser Processing

All utility tools on DeepEngineerHub (including Image to PDF, Text Formatters, JSON Converters, and Encryptors) execute 100% locally within your client browser using WebAssembly and JavaScript. No uploaded images, text, or documents are transmitted, collected, or stored on remote servers.

2. Limitation of Liability ("As-Is" Provision)

Tools and services are provided free of charge for convenience and educational purposes "as-is" without warranties of any kind. DeepEngineerHub shall not be held liable for any data loss, formatting inconsistencies, or indirect damages resulting from tool usage.

3. Open Source & Third-Party Software

Certain utilities utilize open-source client libraries (such as jsPDF, Mermaid.js, Pyodide) licensed under MIT, Apache, or BSD open licenses. All intellectual property remains with their respective copyright holders.